/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.skatiger.jrove.tag;

import java.io.IOException;
import java.util.List;

import javax.el.ELException;

import org.skatiger.jrove.JroveContext;
import org.skatiger.jrove.JroveException;
import org.skatiger.jrove.MetaRuleset;
import org.skatiger.jrove.component.UIComponent;
import org.skatiger.jrove.component.UIViewRoot;
import org.skatiger.jrove.component.core.FacetHandler;
import org.skatiger.jrove.renderer.Renderer;
import org.skatiger.jrove.resource.Resource;
import org.skatiger.jrove.resource.ResourceAnnotationHelper;
import org.skatiger.jrove.util.ComponentSupport;
import org.skatiger.jrove.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ComponentHandler extends MetaTagHandler {

    private final static Logger logger = LoggerFactory
            .getLogger(ComponentHandler.class);

    private final String componentClass;
    private final String rendererType;
    private final TagAttribute id;

    public ComponentHandler(ComponentConfig config) {
        super(config);
        this.componentClass = config.getComponentClass();
        this.rendererType = config.getRendererType();
        this.id = this.getAttribute("id");
    }

    public final void apply(JroveContext ctx, UIComponent parent) throws IOException,
            JroveException,
            ELException {
        // make sure our parent is not null
        if (parent == null) {
            throw new TagException(this.tag, "Parent UIComponent was null");
        }

        // possible facet scoped
        String facetName = this.getFacetName(ctx, parent);

        // our id
        String id = ctx.generateUniqueId(this.tagId);

        // grab our component
        UIComponent c = ComponentSupport.findChildByTagId(parent, id);
        boolean componentFound = false;
        if (c != null) {
            componentFound = true;
            // mark all children for cleaning
            if (logger.isDebugEnabled()) {
                logger.debug(this.tag + " Component[@=" + id
                        + "] Found, marking children for cleanup");
            }
            ComponentSupport.markForDeletion(c);
        } else {
            c = this.createComponent(ctx);
            if (logger.isDebugEnabled()) {
                logger.debug(this.tag + " Component[" + id + "] Created: "
                        + c.getClass().getName());
            }
            this.setAttributes(ctx, c);

            // mark it owned by a facelet instance
            c.getAttributes().put(ComponentSupport.MARK_CREATED, id);

            // assign our unique id

            String uid = null;
            if (this.id != null) {// get id from tag attribute id
                uid = this.id.getValue(ctx).trim();
                c.getAttributes().put(ComponentSupport.NAMED_COMPONENT, uid);
            }
            if (StringUtils.isBlank(uid)) { // get id from viewRoot
                                            // createUniqueId method
                UIViewRoot root = ComponentSupport.getViewRoot(ctx, parent);
                if (root != null) {
                    uid = root.createUniqueId(ctx.getViewContext(), null);
                }
            }
            c.setId(uid);
            if (this.rendererType != null) {
                c.setRendererType(this.rendererType);
            }
            // hook method
            this.onComponentCreated(ctx, c, parent);
        }

        // first allow c to get populated
        this.applyNextHandler(ctx, c);

        // finish cleaning up orphaned children
        if (componentFound) {
            ComponentSupport.finalizeForDeletion(c);
            if (facetName == null) {
                parent.getChildren().remove(c);
            }
        }

        this.onComponentPopulated(ctx, c, parent);

        // add to the tree afterwards
        // this allows children to determine if it's
        // been part of the tree or not yet
        if (facetName == null) {
            parent.getChildren().add(c);
        } else {
            parent.getFacets().put(facetName, c);
        }
        processResourceDependences(ctx, c);
    }

    protected void processResourceDependences(JroveContext ctx, UIComponent c) {

        UIViewRoot viewRoot = ComponentSupport.getViewRoot(ctx, c);
        {// process component resource Annotation
            List<Resource> componentResources = ResourceAnnotationHelper
                    .getResourceDependences(c.getClass());
            if (!componentResources.isEmpty()) {
                viewRoot.addResources(componentResources);
            }
        }
        {// process renderer resource Annotation
            Renderer renderer = ctx.getViewContext().getRenderer(
                    c.getRendererType());
            if (renderer != null) {
                List<Resource> rendererResources = ResourceAnnotationHelper
                        .getResourceDependences(renderer.getClass());
                if (!rendererResources.isEmpty()) {
                    viewRoot.addResources(rendererResources);
                }
            }
        }
    }

    protected void applyNextHandler(JroveContext ctx, UIComponent c) throws IOException,
            JroveException,
            ELException {
        // first allow c to get populated
        this.nextHandler.apply(ctx, c);
    }

    protected UIComponent createComponent(JroveContext ctx) {
        return ctx.getViewContext().createComponent(this.componentClass);
    }

    @Override
    protected MetaRuleset createMetaRuleset(Class<?> type) {
        MetaRuleset m = super.createMetaRuleset(type);

        // ignore standard component attributes
        m.ignore("id");

        // add auto wiring for attributes
        m.addRule(ComponentRule.INSTANCE);

        return m;
    }

    /**
     * Return the Facet name we are scoped in, otherwise null
     * 
     * @param jroveContext
     * @return
     */
    protected final String getFacetName(JroveContext jroveContext,
            UIComponent parent) {
        return (String) parent.getAttributes().get(FacetHandler.KEY);
    }

    protected String getId(JroveContext jroveContext) {
        if (this.id != null) {
            return this.id.getValue(jroveContext);
        }
        return jroveContext.generateUniqueId(this.tagId);
    }

    protected void onComponentCreated(JroveContext jroveContext,
            UIComponent c,
            UIComponent parent) {
        // do nothing
    }

    protected void onComponentPopulated(JroveContext jroveContext,
            UIComponent c,
            UIComponent parent) {
        // do nothing
    }

    @Override
    public String toString() {
        return "component handler for component-class " + componentClass;
    }

}
